redis持久化

您所在的位置:网站首页 redis rdp aof redis持久化

redis持久化

#redis持久化| 来源: 网络整理| 查看: 265

什么是持久化?

Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。但是一旦进程退出,Redis 的数据就会丢失。持久化就是把数据存在磁盘里,出了问题重启能够重新读到数据

redis持久化分类

Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据保存到磁盘中

AOF :( append only file )持久化以独立日志的方式记录每次写命令,并在 Redis 重启时在重新执行 AOF 文件中的命令以达到恢复数据的目的。AOF 的主要作用是解决数据持久化的实时性。 RDB :把当前 Redis 进程的数据生成时间点快照( point-in-time snapshot ) 保存到存储设备的过程。

本篇文章介绍AOF

AOF持久化介绍及流程

AOF通过4点实现持久化:

写入缓存:每次执行命令后,进行append操作写入AOF缓存 同步磁盘:AOF 缓冲区根据对应的策略向硬盘进行同步操作。 AOF重写:随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。 重启加载: 当 Redis 重启时,可以加载 AOF 文件进行数据恢复。 写入缓存

每次执行命令都是通过call(),call的时候会把命令写入aof缓存,也就是server.aof_buf

调用链: call() -> propogate() -> feedAppendOnlyFile

void call(client *c, int flags) { ... propagate(c->cmd,c->db->id,c->argv,c->argc,propagate_flags); } void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags) { ... if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF) feedAppendOnlyFile(cmd,dbid,argv,argc); ... } 复制代码

我们看一下feedAppendOnlyFile()这个函数

feedAppendOnlyFile() void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { 把命令解析编码,比较复杂, buf = catAppendOnlyGenericCommand(buf,argc,argv); 然后存入server.aof_buf server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf)); 如果子进程正在重写AOF,就把buf写入server.aof_rewrite_buf_blocks链表 if (server.child_type == CHILD_TYPE_AOF) aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); } 复制代码 解析命令

buf = catAppendOnlyGenericCommand(buf,argc,argv);

该函数主要工作就是解析命令,把传入的cmd和argv,argc解析成"*3\r\n3\r\nSET\r\n5\r\nmykey\r\n$7\r\nmyvalue\r\n"的样子,存在buff里

写入缓存

server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf)); 这句把解析好的命令写入缓存,接下来要同步给磁盘了

如果子进程正在重写AOF文件,则把解析好的命令写入server.aof_rewrite_buf_blocks链表

server.child_type表示子进程正在进行什么工作,在AOF重写(rewrite)过程中会创建子进程执行重写工作,这个在下面介绍AOF重写的时候会解释这里

同步磁盘

同步磁盘的操作在函数flushAppendOnlyFike()中,

flushAppendOnlyFile 函数的行为由 redis.conf 配置中的 appendfsync 选项的值来决定。该选项有三个可选值,分别是 always、everysec 和 no:

always:Redis 在每个事件循环都要将 AOF 缓冲区中的所有内容写入到 AOF 文件,并且同步 AOF 文件,所以 always 的效率是 appendfsync 选项三个值当中最差的一个,但从安全性来说,也是最安全的。当发生故障停机时,AOF 持久化也只会丢失一个事件循环中所产生的命令数据。 everysec:Redis 在每个事件循环都要将 AOF 缓冲区中的所有内容写入到 AOF 文件中,并且每隔一秒就要在子线程中对 AOF 文件进行一次同步。从效率上看,该模式足够快。当发生故障停机时,只会丢失一秒钟的命令数据。 no:Redis 在每一个事件循环都要将 AOF 缓冲区中的所有内容写入到 AOF 文件。而 AOF 文件的同步由操作系统控制。这种模式下速度最快,但是同步的时间间隔较长,出现故障时可能会丢失较多数据。 write和fsync

Linux 系统下 write 操作会触发延迟写( delayed write )机制。Linux 在内核提供页缓存区用来提供硬盘 IO 性能。write 操作在写入系统缓冲区之后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或者达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。

而 fsync 针对单个文件操作,对其进行强制硬盘同步,fsync 将阻塞直到写入磁盘完成后返回,保证了数据持久化。

appendfsync的三个值代表着三种不同的调用 fsync的策略。调用fsync周期越频繁,读写效率就越差,但是相应的安全性越高,发生宕机时丢失的数据越少。

介绍一些成员变量的作用:

server.aof_fsync:上述三个AOF级别 server.aof_fd: server.aof_current_size: server.aof_fsync_offset: server.aof_last_fsync: server.unixtime: void flushAppendOnlyFile(int force) { // 1.写入fd nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); // 2.同步磁盘 if (server.aof_fsync == AOF_FSYNC_ALWAYS) { redis_fsync(server.aof_fd) server.aof_fsync_offset = server.aof_current_size; server.aof_last_fsync = server.unixtime; } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && server.unixtime > server.aof_last_fsync)) { if (!sync_in_progress) { aof_background_fsync(server.aof_fd); server.aof_fsync_offset = server.aof_current_size; } server.aof_last_fsync = server.unixtime; } } 复制代码

可以看到,flushAppendOnlyFile()函数步骤如下:

调用aofWrite()将缓存内容写入server.aof_fd,aofWrite就是write() 函数的一个包装 根据aof模式调用下面方法之一: AOF_FSYNC_ALWAYS:调用redis_fsync(server.aof_fd)直接同步 AOF_FSYNC_EVERYSEC:调用aof_background_fsync(server.aof_fd)在后台同步,后台同步过程下一个小节介绍

这里还有一些规则细节需要介绍:

flushAppendOnlyFile()函数执行以下两个工作:

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。 SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。 在AOF_FSYNC_EVERYSEC模式下:

每当 flushAppendOnlyFile 函数被调用时, 可能会出现以下四种情况:

子线程正在执行 SAVE ,并且:

这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。

子线程没有在执行 SAVE ,并且:

上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。 根据以上说明可以知道, 在“每一秒钟保存一次”模式下, 如果在情况 1 中发生故障停机, 那么用户最多损失小于 2 秒内所产生的所有数据。

如果在情况 2 中发生故障停机, 那么用户损失的数据是可以超过 2 秒的。

redis_fsync就是系统调用fsync,没啥说的,主要看后台同步的过程

后台同步 aof_background_fsync(server.aof_fd) 调用链 aof_background_fsync(server.aof_fd) bioCreateFsyncJob(server.aof_fd); struct bio_job *job = zmalloc(sizeof(*job)); job->fd = fd; bioSubmitJob(BIO_AOF_FSYNC, job); job->time = time(NULL); pthread_mutex_lock(&bio_mutex[BIO_AOF_FSYNC]); listAddNodeTail(bio_jobs[BIO_AOF_FSYNC],job); bio_pending[BIO_AOF_FSYNC]++; pthread_cond_signal(&bio_newjob_cond[BIO_AOF_FSYNC]); pthread_mutex_unlock(&bio_mutex[BIO_AOF_FSYNC]); 复制代码

后台有一个bio线程,在执行bioProcessBackgroundJobs() ,不停地redis_fsync(job->fd)

AOF重写

AOF重写比较复杂,篇幅较长

为什么要AOF重写?

redis长时间运行,随着执行的命令增加,AOF 文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的 AOF 文件很可能对 Redis 甚至宿主计算机造成影响。所以redis会定时重写AOF文件,压缩大小。新旧两个 AOF 文件所保存的 Redis 状态相同,但是新的 AOF 文件不会包含任何浪费空间的荣誉命令,所以新 AOF 文件的体积通常比旧 AOF 文件的体积要小得很多。

image.png 如上图所示,重写前要记录名为list的键的状态,AOF 文件要保存五条命令,而重写后,则只需要保存一条命令

AOF重写概括

redis的AOF重写是由主进程fork出一条子进程来执行的

子进程重写一个新的AOF文件,在子进程进行 AOF 重写期间,Redis主进程继续接收客户端命令,会对现有数据库状态进行修改,从而导致数据当前状态和 重写后的 AOF 文件所保存的数据库状态不一致。

为此,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。

image.png

在AOF重写过程中,AOF重写缓冲区中的数据会被主进程写入管道发送给子进程,子进程写入新的AOF文件,完成任务。主进程发现子进程结束,把AOF重写缓冲区中剩下的写入新AOF文件,替换旧的AOF文件,然后继续接收命令并执行。

AOF重写详细过程——深入源码

重写过程在rewriteAppendOnlyFileBackground函数中

int rewriteAppendOnlyFileBackground(void) { pid_t childpid; // 创建管道 aofCreatePipes() if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) { char tmpfile[256]; /* Child */ redisSetProcTitle("redis-aof-rewrite"); redisSetCpuAffinity(server.aof_rewrite_cpulist); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); // 执行aof重写 rewriteAppendOnlyFile(tmpfile) sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite"); exitFromChild(0); } else { /* Parent */ server.aof_rewrite_scheduled = 0; server.aof_rewrite_time_start = time(NULL); server.aof_selected_db = -1; replicationScriptCacheFlush(); return C_OK; } return C_OK; /* unreached */ } 复制代码 aofCreatePipes()创建aof父子进程之间通讯需要的管道 这里通过redisFork()函数创建了一个子进程 子进程通过rewriteAppendOnlyFile()进行AOF重写工作

下面我们分别看看父进程和子进程做了什么

子进程 server.in_fork_child = CHILD_TYPE_AOF; setOOMScoreAdj(CONFIG_OOM_BGCHILD); setupChildSignalHandlers(); closeChildUnusedResourceAfterFork(); redisSetProcTitle("redis-aof-rewrite"); redisSetCpuAffinity(server.aof_rewrite_cpulist); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); // 重写逻辑 rewriteAppendOnlyFile(tmpfile) exitFromChild(0); 复制代码

子进程重写的逻辑在rewriteAppendOnlyFile()函数里

int rewriteAppendOnlyFile(char *filename) { // 1. 创建一个临时文件,名字是temp-rewriteaof-pid.aof snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); fp = fopen(tmpfile,"w"); rioInitWithFile(&aof,fp); // 遍历每个db里的每个kv对,为其生成一条set语句 // 然后调用aof的write函数把生成的语句写入aof.io.file.fp // r->io.file.autosync决定了是否每次写后刷新 rewriteAppendOnlyFileRio(&aof) fflush(fp) fsync(fileno(fp)) // 父进程还在处理命令,这时候可能又有新的命令需要记录了 // 从管道接收父进程发送的数据 int nodata = 0; mstime_t start = mstime(); while(mstime()-start < 1000 && nodata < 20) { if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3